package com.avcompris.util.reflect;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static com.avcompris.util.reflect.AbstractClassTestUtils.loadFileClasses;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isStatic;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.apache.commons.lang3.ArrayUtils.contains;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;

import org.apache.log4j.Logger;
import org.junit.Test;

import com.avcompris.util.junit.AvcParameterized;

/**
 * tests on Java classes to check
 * if the loaded Log4J logger is <tt>static</tt>
 * and with the right class name.
 * 
 * @author David Andriana Copyright Avantage Compris SARL 2009 ©
 */
public abstract class AbstractLog4JLoggerTest {

	/**
	 * the Java class to test.
	 */
	private final Class<?> clazz;

	/**
	 * constructor for a parameterized test class.
	 * 
	 * @param clazz the class to test
	 */
	protected AbstractLog4JLoggerTest(final Class<?> clazz) throws Exception {

		this.clazz = nonNullArgument(clazz);
	}

	/**
	 * construct the right instance+method
	 * parameters for the @{@link AvcParameterized}
	 * test class that inherits this one, from a list of classes to test
	 * 
	 * @param targets the list of classes or instance objects
	 * @return the right class parameters for the @{@link AvcParameterized}
	 * test class that inherits this one 
	 */
	protected static Collection<Object[]> parametersDoNotScanProject(
			final Class<?>... targets) {

		nonNullArgument(targets, "targets");

		final Collection<Object[]> params = new ArrayList<Object[]>();

		// declared classes

		for (final Class<?> target : targets) {

			if (target.isInterface()) {
				continue;
			}

			addToTests(params, target);
		}

		return params;
	}

	/**
	 * add a class to params.
	 * 
	 * @param params the current params, to add the new params to
	 * @param instance the instance to check, or <tt>null</tt>
	 * @param c the class to test
	 * @param targets the declared Java class or instance to test
	 */
	private static void addToTests(
			final Collection<Object[]> params,
			final Class<?> c) {

		nonNullArgument(params, "params");
		nonNullArgument(c, "class");

		if (c.isInterface()) {
			throw new IllegalArgumentException(
					"May not test fields in Interface: " + c.getName());
		}

		// We *do not* test against super classes here.

		params.add(new Object[]{
			c
		});
	}

	/**
	 * construct the right class
	 * parameters for the @{@link AvcParameterized}
	 * test class that inherits this one, from a list of classes to test.
	 * 
	 * @param targets the list of classes or instance objects
	 * @return the right class parameters for the @{@link AvcParameterized}
	 * test class that inherits this one 
	 */
	protected static Collection<Object[]> parametersScanProject(
			final Class<?>... targets) throws ClassNotFoundException {

		// declared classes

		final Collection<Object[]> params = parametersDoNotScanProject(targets);

		// add all sources

		final File sourceDir = new File("src/main/java");

		final Class<?>[] fileClasses = loadFileClasses(sourceDir);

		for (final Class<?> fileClass : fileClasses) {

			// add only sources that were not declared

			boolean foundInTargets = false;

			for (final Object target : targets) {

				if (target instanceof Class<?>) {

					if (fileClass.equals(target)) {

						foundInTargets = true;

						break;
					}

				} else {

					if (fileClass.equals(target.getClass())) {

						foundInTargets = true;

						break;
					}
				}
			}

			if (!foundInTargets && !fileClass.isInterface()) {

				addToTests(params, fileClass);
			}
		}

		// assert all declared Java class or instance targets can be found in 
		// local Java source files

		for (final Class<?> target : targets) {

			if (!contains(fileClasses, target)) {

				throw new IllegalArgumentException(
						"Declared target class was not found in Java source files: "
								+ target.getName());
			}
		}

		// end

		return params;
	}

	/**
	 * @return a {@link String} representation of this test class, to use by
	 *         {@link org.junit.runner.Description}.
	 */
	@Override
	public final String toString() {

		return clazz.getName();
	}

	@Test
	public final void testLog4JLoggerIsNormalized() throws Throwable {

		for (final Field field : clazz.getDeclaredFields()) {

			field.setAccessible(true);

			if (Logger.class.equals(field.getType())) {

				final String name = field.getName();

				final int modifiers = field.getModifiers();

				assertTrue("Log4J logger Field is not static: " + name,
						isStatic(modifiers));

				assertTrue("Log4J logger Field is not final: " + name,
						isFinal(modifiers));

				assertTrue("Log4J logger Field is not private: " + name,
						isPrivate(modifiers));

				final Logger logger = (Logger) field.get(null);

				assertEquals(
						"Log4J logger Field has not the same name as its enclosing class.",
						clazz.getName(), logger.getName());
			}
		}
	}
}
